#include "intdensematrix.h"


void intDenseMatrix::errorIfEmpty(void)
{
    if (numrows*numcols == 0)
    {
        std::cout << "Error in 'intDenseMatrix' object: cannot perform operation on empty matrix" << std::endl;
        abort();
    }
}

intDenseMatrix::intDenseMatrix(long long int numberofrows, long long int numberofcolumns)
{
    numrows = numberofrows;
    numcols = numberofcolumns;
    myvalues = std::shared_ptr<int>(new int[numcols*numrows]);
}

intDenseMatrix::intDenseMatrix(long long int numberofrows, long long int numberofcolumns, int initvalue)
{
    numrows = numberofrows;
    numcols = numberofcolumns;
    int* myvaluesptr = new int[numcols*numrows];
    
    for (long long int i = 0; i < numcols*numrows; i++)
        myvaluesptr[i] = initvalue;
    
    myvalues = std::shared_ptr<int>(myvaluesptr);
}

intDenseMatrix::intDenseMatrix(long long int numberofrows, long long int numberofcolumns, const std::vector<int> valvec)
{
    numrows = numberofrows;
    numcols = numberofcolumns;
    int* myvaluesptr = new int[numcols*numrows];
    
    for (long long int i = 0; i < numcols*numrows; i++)
        myvaluesptr[i] = valvec[i];
    
    myvalues = std::shared_ptr<int>(myvaluesptr);
}

intDenseMatrix::intDenseMatrix(long long int numberofrows, long long int numberofcolumns, int init, int step)
{
    numrows = numberofrows;
    numcols = numberofcolumns;
    int* myvaluesptr = new int[numcols*numrows];
    
    for (long long int i = 0; i < numcols*numrows; i++)
        myvaluesptr[i] = init+i*step;
    
    myvalues = std::shared_ptr<int>(myvaluesptr);
}

long long int intDenseMatrix::countPositive(void)
{
    int* myvaluesptr = myvalues.get();
    
    long long int numpositive = 0;
    for (long long int i = 0; i < numrows*numcols; i++)
    {
        if (myvaluesptr[i] >= 0)
            numpositive++;            
    }
    return numpositive;
}

long long int intDenseMatrix::countOccurences(long long int value)
{
    int* myvaluesptr = myvalues.get();
    
    long long int num = 0;
    for (long long int i = 0; i < numcols*numrows; i++)
    {
        if (myvaluesptr[i] == value)
            num++;
    }
    return num;
}

intDenseMatrix intDenseMatrix::removeValue(long long int toremove)
{
    int* myvaluesptr = myvalues.get();
    
    long long int num = countOccurences(toremove);
    
    intDenseMatrix output(numcols*numrows-num,1);
    int* outvals = output.getValues();
    
    long long int index = 0;
    for (long long int i = 0; i < numcols*numrows; i++)
    {
        if (myvaluesptr[i] != toremove)
        {
            outvals[index] = myvaluesptr[i];
            index++;
        }
    }
    return output;
}

std::vector<int> intDenseMatrix::countAllOccurences(int maxintval)
{
    int* myvaluesptr = myvalues.get();
    
    std::vector<int> output(maxintval+1, 0);
    
    for (long long int i = 0; i < numcols*numrows; i++)
        output[myvaluesptr[i]]++;
    
    return output;
}

std::vector<std::vector<int>> intDenseMatrix::findAllOccurences(int maxintval)
{
    int* myvaluesptr = myvalues.get();
 
    std::vector<int> numoccurences = countAllOccurences(maxintval);
    
    std::vector<std::vector<int>> output(numoccurences.size());
    for (int i = 0; i < numoccurences.size(); i++)
        output[i] = std::vector<int>(numoccurences[i]);
    
    std::vector<int> indexes(numoccurences.size(), 0);
    for (long long int i = 0; i < numcols*numrows; i++)
    {
        int curvalue = myvaluesptr[i];
        output[curvalue][indexes[curvalue]] = i;
        indexes[curvalue]++;
    }
    
    return output;
}

long long int intDenseMatrix::sum(void)
{
    int* myvaluesptr = myvalues.get();
    
    long long int summed = 0;
    for (long long int i = 0; i < numcols*numrows; i++)
        summed += myvaluesptr[i];
    return summed;
}

std::vector<int> intDenseMatrix::minmax(void)
{
    errorIfEmpty();

    int* myvaluesptr = myvalues.get();
    
    int minval = myvaluesptr[0];
    int maxval = myvaluesptr[0];

    for (long long int i = 1; i < numrows*numcols; i++)
    {
        if (myvaluesptr[i] > maxval)
            maxval = myvaluesptr[i];
        if (myvaluesptr[i] < minval)
            minval = myvaluesptr[i];
    }
    return {minval, maxval};
}

int intDenseMatrix::max(void)
{
    errorIfEmpty();

    int* myvaluesptr = myvalues.get();
    
    int maxval = myvaluesptr[0];

    for (long long int i = 1; i < numrows*numcols; i++)
    {
        if (myvaluesptr[i] > maxval)
            maxval = myvaluesptr[i];
    }
    return maxval;
}

void intDenseMatrix::print(void)
{
    printSize();

    int* myvaluesptr = myvalues.get();
    for (long long int i = 0; i < numrows; i++)
    {
        for (long long int j = 0; j < numcols; j++)
            std::cout << myvaluesptr[i*numcols+j] << " ";
        std::cout << std::endl;
    }
    std::cout << std::endl;
}

void intDenseMatrix::printSize(void)
{
    std::cout << "Matrix size is " << numrows << "x" << numcols << std::endl;
}

int* intDenseMatrix::getValues(void)
{
    return myvalues.get();
}

intDenseMatrix intDenseMatrix::getTranspose(void)
{
    intDenseMatrix output(numcols, numrows);

    int* myvaluesptr = myvalues.get();
    int* outvaluesptr = output.myvalues.get();

    for (long long int row = 0; row < numrows; row++)
    {
        for (long long int col = 0; col < numcols; col++)
            outvaluesptr[col*numrows+row] = myvaluesptr[row*numcols+col];
    }

    return output;
}

intDenseMatrix intDenseMatrix::duplicateAllRowsTogether(int n)
{
    intDenseMatrix output(numrows*n, numcols);
    
    int* myvaluesptr = myvalues.get();
    int* outvaluesptr = output.myvalues.get();
        
    long long int matsize = numrows*numcols;
    
    for (int duplicate = 0; duplicate < n; duplicate++)
    {
        for (long long int i = 0; i < matsize; i++)
            outvaluesptr[matsize*duplicate + i] = myvaluesptr[i];
    }
    
    return output;
}

intDenseMatrix intDenseMatrix::duplicateRowsOneByOne(int n)
{
    intDenseMatrix output(numrows*n, numcols);
    
    int* myvaluesptr = myvalues.get();
    int* outvaluesptr = output.myvalues.get();
    
    for (long long int row = 0; row < numrows; row++)
    {
        for (int duplicate = 0; duplicate < n; duplicate++)
        {
            for (long long int col = 0; col < numcols; col++)
                outvaluesptr[row*(numcols*n) + duplicate*numcols + col] = myvaluesptr[row*numcols+col];
        }
    }
    
    return output;
}

intDenseMatrix intDenseMatrix::duplicateAllColsTogether(int n)
{
    intDenseMatrix output(numrows, numcols*n);

    int* myvaluesptr = myvalues.get();
    int* outvaluesptr = output.myvalues.get();

    long long int ind = 0;
    for (long long int i = 0; i < numrows; i++)
    {
        for (int duplicate = 0; duplicate < n; duplicate++)
        {
            for (long long int j = 0; j < numcols; j++)
            {
                outvaluesptr[ind] = myvaluesptr[i*numcols+j];
                ind++;   
            }
        }
    }

    return output;
}

intDenseMatrix intDenseMatrix::duplicateColsOneByOne(int n)
{
    intDenseMatrix output(numrows, numcols*n);

    int* myvaluesptr = myvalues.get();
    int* outvaluesptr = output.myvalues.get();

    long long int ind = 0;
    for (long long int i = 0; i < numrows; i++)
    {
        for (long long int j = 0; j < numcols; j++)
        {
            for (int duplicate = 0; duplicate < n; duplicate++)
            {
                outvaluesptr[ind] = myvaluesptr[i*numcols+j];
                ind++;   
            }
        }
    }

    return output;
}

intDenseMatrix intDenseMatrix::extractRows(std::vector<int>& selected)
{
    long long int numselected = selected.size();

    intDenseMatrix output(numselected, numcols);

    int* vals = getValues();
    int* outvals = output.getValues();
    for (long long int i = 0; i < numselected; i++)
    {
        long long int row = selected[i];
        for (long long int j = 0; j < numcols; j++)
            outvals[i*numcols+j] = vals[row*numcols+j];
    }

    return output;
}

intDenseMatrix intDenseMatrix::extractCols(std::vector<int>& selected)
{
    long long int numselected = selected.size();

    intDenseMatrix output(numrows, numselected);

    int* vals = getValues();
    int* outvals = output.getValues();
    for (long long int i = 0; i < numrows; i++)
    {
        for (long long int j = 0; j < numselected; j++)
            outvals[i*numselected+j] = vals[i*numcols+selected[j]];
    }

    return output;
}
    

